Network Programmability and Automation 101

Enginyeria La Salle, May 18

Christian Adell @chadell0

Agenda

Time Topic
Day 1
15:30 - 16:00 Network Programmability & Automation
16:00 - 16:30 Ansible 101
16:30 - 17:00 Exercise 1
Day 2
15:30 - 16:00 Ansible 102
16:00 - 17:00 Exercise 2

Day 1

Network Programmability and Automation 101

What is Software Defined Networking?

Openflow

Network Functions Virtualization

Virtual switching

Network virtualization

Device APIs

Network Automation

Bare-metal switching

Data center network fabrics

SD-WAN

Controller networking

Network Automation

Why

  • Simplified Architectures
  • Deterministic Outcomes
  • Business Agility

Types

  • Device Provisioning
  • Data Collection
  • Migrations
  • Configuration Management
  • Compliance
  • Reporting
  • Troubleshooting

Application Programming Interfaces (APIs)

  • SNMP
  • SSH/Telnet and the CLI
  • NETCONF
  • RESTful APIs

Data Formats, Data Models and Config Templates

XML

<rpc-reply xmlns:junos="http://xml.juniper.net/junos/13.3R5/junos">
    <software-information>
        <host-name>M320-TEST-re0</host-name>
        <product-model>m320</product-model>
        <product-name>m320</product-name>
        <junos-version>13.3R5.9</junos-version>
    </software-information>
    <cli>
        <banner>{master}</banner>
    </cli>
</rpc-reply>

YAML

---
parameter_defaults: 
  ControlPlaneDefaultRoute: "192.0.2.1"
  ControlPlaneSubnetCidr: 24
  DnsServers:
    - "192.168.23.1"
  EC2MetadataIp: "192.0.2.1" 
  ExternalAllocationPools:
    - end: "10.0.0.250"
      start: "10.0.0.4"
  ExternalNetCidr: "10.0.0.1/24"
  NeutronExternalNetworkBridge: ""

JSON

{
  "parameter_defaults": {
    "ControlPlaneDefaultRoute": "192.0.2.1", 
    "ControlPlaneSubnetCidr": "24", 
    "DnsServers": [
        "192.168.23.1"
    ], 
    "EC2MetadataIp": "192.0.2.1", 
    "ExternalAllocationPools": [
        {
            "end": "10.0.0.250", 
            "start": "10.0.0.4"
        }
    ], 
    "ExternalNetCidr": "10.0.0.1/24", 
    "NeutronExternalNetworkBridge": ""
  }
}

YANG

 module configuration {
  namespace "http://xml.juniper.net/xnm/1.1/xnm";
  prefix junos;
  organization
    "Juniper Networks, Inc.";
  revision "2015-09-11" {
    description "Initial revision";
  }
  typedef ipv4addr {
    type string;
  }
  grouping juniper-config {
    container backup-router {
      description "IPv4 router to use while booting";
      leaf address {
        description "Address of router to use while booting";
        type ipv4addr;
        mandatory true;
      }
      presence "enable backup-router";
      leaf-list destination {
        description "Destination network reachable through the router";
        type ipv4prefix;
      }
    }
    ...

https://raw.githubusercontent.com/Juniper/yang/master/14.2/configuration.yang

JINJA

{% for key, value in vlanDict.iteritems() -%}
vlan {{ key }}
    name {{ value }}
{% endfor %}
>>> vlanDict = {123: 'TEST-VLAN-123', 234: 'TEST-VLAN-234', 345: 'TEST-VLAN-345'}
>>> from jinja2 import Environment
>>> env = Environment(loader=FileSystemLoader('./Templates/'))
>>> template = env.get_template('ourtemplate')
>>> print template.render(vlanDict)

vlan 123
    name TEST-VLAN-123
vlan 234
    name TEST-VLAN-234
vlan 345
    name TEST-VLAN-345

Ansible 101

Review of automation tools

  • Ansible
  • Chef
  • Puppet
  • Salt
  • StackStorm

Understanding how Ansible works

  • Automating servers
    • Distributed execution
    • Copy via SSH python code and runs in every device
  • Automating network devices
    • Centralised execution
    • Runs python code locally and reach network devices by SNMP, SSH or APIs

Basic files and defaults

  • Inventory file: Contains the devices (ip or fqdn) that will be automated (and associated variables)
    • /etc/ansible/hosts
    • ANSIBLE_INVENTORY
    • -i, --inventory-file
  • Variable files:
    • Group variables
      • group_vars/{name of group}.yml or
      • group_vars/{name of group}/{variables}.yml
    • Host variables
      • host_vars/{name of group}.yml or
      • host_vars/{name of group}/{variables}.yml

Inventory file

[barcelona-dc]
switch01
switch02

[madrid-dc]
172.31.200.1
switch03

[barcelona-cpe]
vmx1

[madrid-cpe]
172.22.3.1

[barcelona:children]
barcelona-dc
barcelona-cpe

Assigning variables

[all:vars]
ntp_server=10.20.30.4

[barcelona:vars]
ntp_server=192.168.0.1

[madrid:vars]
ntp_server=10.0.0.1

or

[barcelona-dc]
switch01 ntp_server=192.168.0.3
switch02

Variables' file

File: group_vars/barcelona-dc.yml

---
snmp:
    contact: Ausias March
    location: Barcelona Data Center, Passeig Colon
    communities:
        - community: public
          type: ro
        - community: private
          type: rw

Executing an Ansible Playbook

It's the file that contain your automation instructions

---
    - name: PLAY 1 - Configure Interface Speed
      hosts: barcelona-dc
      connection: local
      gather_facts: no

      tasks:

        - name: TASK1 - Get interface information
          ios_command:
            commands:
                - show run | include interfaces
            provider:
                username: myusername
                password: mypassword
                host: "{{ inventory_hostname }}"

Exercise 1

Goal

Experiment with basic Ansible automation

All you need is here: https://github.com/chadell/ansible-cumulus-vyos

Scenario

TODO

From the mgmt server:

  1. Run the example playbook (exercise1.yml) against the targeted hosts (inventory.cfg)
    • Analyse output and what the playbook is doing
  2. Update the hostname of all the devices to match the fqdn: router01, router02, switch, server, oob-switch
    • Make a PR to the Github repository with your playbook (be aware of identifying yourself)
  3. Contribute to improve this workshop by fixing errors, typos or promoting improvements by PRs

Agenda

Time Topic
Day 1
15:30 - 16:00 Network Programmability & Automation
16:00 - 16:30 Ansible 101
16:30 - 17:00 Exercise 1
Day 2
15:30 - 16:00 Ansible 102
16:00 - 17:00 Exercise 2

Day 2

Writing Ansible Playbooks

Core modules

  • command: used to send exec-level commands
    • ios_command, vyos_command, junos_command, and so on
  • config: used to send configuration commands
    • ios_config, vyos_config, junos_config, and so on
  • facts: used to gather information from network devices
    • ios_facts, vyos_facts, junos_facts, and so on

Note: to find out the parameters of each module (plus some examples), you can use the ansible-doc utility: $ ansible-doc ios_config

Creating and using configuration templates

  1. Creating variable files
  2. Creating Jinja templates
  3. Generating network configuration files

Creating variable files (1)

group_vars/barcelona-dc.yml

---
snmp:
    contact: Ausias March
    location: Barcelona Data Center, Passeig Colon
    communities:
        - community: public
          type: ro
        - community: privat
          type: rw

Creating variable files (2)

group_vars/madrid-dc.yml

---
snmp:
    contact: Francisco de Quevedo
    location: Madrid Data Center, Paseo de la Castellana
    communities:
        - community: publico
          type: ro
        - community: privado
          type: rw

Creating variable files (3)

group_vars/all.yml

---
base_provider:
    username: vagrant
    password: vagrant
    host: "{{ inventory_hostname}}"

Creating Jinja templates (1)

templates/snmp/ios.j2

snmp-server location {{ snmp.location }}
snmp-server contact {{ snmp.contact }}
{% for community in snmp.communities %}
snmp-server community {{ community.community }} {{ community.type | upper }}
{% endfor %}

Creating Jinja templates (2)

templates/snmp/junos.j2

set snmp location {{ snmp.location }}
set snmp contact {{ snmp.contact }}
{% for community in snmp.communities %}
{% if community.type | lower == "rw" %}
set snmp community {{ community.community }} authorization read-write
{% elif community.type | lower == "ro" %}
set snmp community {{ community.community }} authorization read-only
{% endif %}
{% endfor %}

Generating network configuration files

We will use the template module. It use the src parameter as the proper template to use and the dest paramter to point to the location where to store the rendered configuration (it assumes the folders already exist)

---
  - name: PLAY 1 - GENERATE SNMP CONFIGURATIONS
    hosts: all
    connection: local
    gather_facts: no

    tasks:
      - name: GENERATE CONFIGS FOR EACH OS
        template:
          src: "./snmp/{{ os }}.j2"
          dest: "./configs/snmp/{{ inventory_hostname }}.cfg

and run it!

$ ansible-playbook -i inventory.cfg snmp.yml

What is os? and inventory_hostname?

  • os is a variable, so for each inventory element the task looks for the value of the os variable. It could be defined in specific files (as pointed out before), or in the inventory file with: ``` [eos] eos-spine1 eos-spine2

[eos:vars] os=eos ```

  • inventory_hostname is just the name of the network device from the inventory file

Ensuring a configuration exists

  1. Idempotency: make the change only when it's needed, so if you run the playbook twice without changes, it will have effect the first time
  2. Using the config module
  3. Understanding check mode, verbosity and limit

Using the config module

Let's use the eos_config module to deploy the SNMP configuration from previous example

  - name: PLAY 2 - ENSURE EOS SNMP CONFIGS ARE DEPLOYED
    hosts: eos
    connection: local
    gather_facts: no

    tasks:
      - name: DEPLOY CONFIGS FOR EOS
        eos_config:
          src: "./configs/snmp/{{ inventory_hostname }}.cfg"
          provider: "{{ base_provider }}"

Notes: 1. This could be the second task of the previous example (we are using the output file as src) 2. We are running agains a subset of hosts (eos), and using their specific module (eos_config) 3. We are using as provider (access credentials) an object defined as a variable for all the devices, such as:

base_provider:
  username: vagrant
  password: vagrant
  host: "{{ inventory_hostname }}

Other options/parameters for config module

  • commands, instead of using src (a file), we could embed a list of commands to be executed in the network device
  • parents, needed when we are working with nested configuration, for instance an interface mode, we reference these dependencies
  • other specific parameters, check them using ansible-doc

Understanding check mode, verbosity and limit

  • Check mode, is the ability to run playbooks in "dry run" mode, the ability of knowing if changes will occur. Use it by enabling the --check when executing the playbook
  • Verbosity, eveyr module returns JSON data with metadata of the comanand and the respone from the device. Use it by enabling the -v flag when running the playbook.
  • Limit, usually you define the hosts to run the playbook against is the hosts paramters, but you can be more concrete by using the --limit option and a list of the groups from the inventory

Gathering and viewing network data

Even Ansible is used often to deploy configurations it also makes possible to automate the collection of data from network devices.

In this part we will analyse two key methods for gathering data: * core facts modules * arbitrary show commands with the command module

Using the core facts modules

The core facts modules return the following data as JSON (so it could be used in the playbook!):

Core facts modules Result
ansible_net_model The model name returned from the device
ansible_net_serialnum The serial number of the remote device
ansible_net_version The operation system version running on the remote device
ansible_net_hostname The configured hostname of the device
ansible_net_config The current active config from the device
ansible_net_interfaces A hash of all interfaces running on the system
ansible_net_neighbors The list of the LLDP neighbors form the remote device

Get fact from network devices

Even by default the gather_facts provides all this information, in network devices that don't let remote python code execution (non Linux based NOS), we have to use specific modules (i.e. ios_facts:

---
  - name: PLAY 1 - COLLECT FACTS FOR IOS
    hosts: iosxe
    connection: local
    gather_facts: no

    tasks:
      - name: COLLECT FACTS FOR IOS
        ios_facts:
          provider: "{{ base_provider }}

Using the debug module

In order to view the facts that are being returned from the module you can run the playbook in verbose mode or simply yse the debug module with the var parameter while referencing a valid facts key:

# play definition omitted
  tasks:
    - name: COLLECT FACTS FROM IOS
      ios_facts:
        provider: "{{ base_provider }}"
    
    - name: DEBUG OS VERSION
      debug:
        var: ansible_net version
    
    - name: DEBUG HOSTNAME
      debug:
        var: ansible_net_hostname

Issuing show commands and writing data to a file

  1. Using the register task attribute

Performing compliance checks

Generating reports

Using 3rd-party Ansible modules

NAPALM modules

Bring your own module

  • Installing 3rd party modules

Exercise 2

Goal

Extend Ansible feautures to provision a network

All you need is here: https://github.com/chadell/ansible-cumulus-vyos

Scenario

TODO

  • list of things
  • list of things